﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "ActionCreateStructureWallHole.h"

#include "GbmpBuiltInCategoryUniIdentitiesNew.h"   
#include "GbmpGraphicsNodeUtils.h"
#include "LevelUtils.h"
#include "GcmpBuiltInCategoryUniIdentities.h"
#include "IGraphicsStyleManager.h"
#include "ISnap.h"
#include "ISnapContext.h"
#include "ISnapSettings.h"
#include "IPickFilter.h"
#include "IDocument.h"
#include "UiViewUtils.h"
#include "IPointPickContext.h"
#include "IPickTarget.h"
#include "IPicker.h"
#include "IPositionCurve2d.h"
#include "GraphicsNodeUtils.h"
#include "IMainWindow.h"
#include "IUiDocument.h"
#include "IUiView.h"
#include "Vector2dUtils.h"
#include "IUserTransaction.h"
#include "IPickPointAction.h"
#include "IInstance.h"
#include "IModelView.h"
#include "IActionInputEventArgs.h"
#include "IPropertyPanelEventArgs.h"
#include "IPropertyPanelEvent.h"
#include "IPropertyWidget.h"
#include "IGraphicsNodeGroup.h"
#include "IFamily.h"
#include "IParameterDefinitionLibrary.h"
#include "IGraphicsLine.h"
#include "IBaseGraphicsElementShapeComponent.h"
#include "AlgorithmMinimalDistance.h"
#include "IGraphicsNodeReference.h"
#include "IElementPosition.h"
#include "IPickResult.h"
#include "IRegenerator.h"
#include "IGraphicsElementShape.h"
#include "AddStructureDynamicPannelUtil.h"
#include "GbmpControlIds.h"
#include "StructureInstanceCmdUtils.h"
#include "PropertyWidgetUtils.h"
#include "StructureFamilyUtils.h"
#include "StructureInstancePresetParameterUtils.h"
#include "GbmpPresetParameterUIUtils.h"
#include "NotificationUtils.h"
#include "IElementStatus.h"

#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"

using namespace gcmp;

namespace
{
    class WallInstanceFilter : public IPickFilter
    {
    public:
        WallInstanceFilter(IDocument* pDoc) : m_pDoc(pDoc) {}
        bool AllowElement(const ElementId& id) const override
        {
            IElement* pElement = m_pDoc->GetElement(id);
            IInstance* pInstance = dynamic_cast<IInstance*>(m_pDoc->GetElement(id));
            if (pInstance == nullptr)
                return false;

            if (!pInstance->GetBasicInformation()->GetCategoryUid().IsValid())
                return false;

            if (pInstance->GetBasicInformation()->GetCategoryUid() == BuiltInCategoryUniIdentities::BICU_STRUCTURE_WALL)
                return true;

            return false;
        }

        bool AllowGraphicsNode(const IGraphicsNodeReference& nodeReference) const override
        {
            OwnerPtr<GraphicsNodeAndTransform> oNodeAndTransform = nodeReference.GetIntersectGraphicsNode();
            if (!oNodeAndTransform)
                return false;

            const IGraphicsNode* pNode = oNodeAndTransform->m_wpGraphicsNode.Get();
            if (!pNode)
                return false;

            if (pNode->GetVisibility() != gcmp::GraphicsNodeVisibility::Always)
                return false;

            return true;
        }

        bool SetPickTargetOption(IPickTarget* pickTarget) override { return true; }

    private:
        IDocument* m_pDoc;
    };
}

ActionCreateStructureWallHole::ActionCreateStructureWallHole(const gcmp::UniIdentity &category, Int32 categorySecondKey, const gcmp::ElementId& familyId, const gcmp::ElementId& instanceTypeId)
    : m_pDoc(nullptr)
    , m_pCurrentView(nullptr)
    , m_isUserCancelled(false)
    , m_category(category)
    , m_categorySecondKey(categorySecondKey)
    , m_familyId(familyId)
    , m_instanceTypeId(instanceTypeId)
    , m_snap(nullptr)
{
}

ActionCreateStructureWallHole::~ActionCreateStructureWallHole()
{
    CleanupTempGraphicsShape();
    // 取消订阅属性表消息
    if (IPropertyWidget *pPropertyWidget = IPropertyWidget::GetPropertyWidget())
    {
        pPropertyWidget->GetInputEvent()->Delete(this);
    }

    // 属性面板
    GbmpPresetParameterUIUtils::UpdatePropertyPanel(ElementId::InvalidID);
    GbmpPresetParameterUIUtils::EnablePropertyPanel(true);
}

void ActionCreateStructureWallHole::InitAction(IUiView* pCurrentView)
{
    SetCursorType(CursorType::CrossHairCursor);
    m_pCurrentView = pCurrentView->GetModelView();
    m_pDoc = GetDocument(pCurrentView);
    DBG_WARN_AND_RETURN_VOID_UNLESS(m_pCurrentView && m_pDoc, L"当前视图或文档为空",L"GDMPLab",L"2024-03-30");

    // ----------------------------------------------------------------------------------------------------------------
    // 预设参数设置
    // ----------------------------------------------------------------------------------------------------------------
    StructureWallHoleInput input = CreateDefaultInput();
    input.Position = Vector3d(0.0, 0.0, 0.0);
    input.CanBeShared = false;
    input.CreationOption = ElementCreationOptions::Transient;
    input.HostId = ElementId::InvalidID;
    IInstance *pInstance = CreateWallHole(input);
    DBG_WARN_AND_RETURN_VOID_UNLESS(pInstance, L"预设参数对象创建失败",L"GDMPLab",L"2024-03-30");
    IElementStatus* pStatus = pInstance->GetStatus();
    DBG_WARN_AND_RETURN_VOID_UNLESS(pStatus, L"无法获取状态集",L"GDMPLab",L"2024-03-30");
    pStatus->SetIsVisible(false);

    // ------------------------------------------------------------------------------------------
    // 更新预设对象
    // ------------------------------------------------------------------------------------------
    SetPreSetElementId(pInstance->GetElementId());

    //预设参数
    OwnerPtr<IUserTransaction> ut = IUserTransaction::Create(m_pDoc, GBMP_TR(L"保存当前创建实例的族类型到预设参数"), true);
    bool bOk = StructureInstancePresetParameterUtils::SetPresetInstanceType(m_pDoc, m_category, m_categorySecondKey, m_instanceTypeId);
    DBG_WARN_AND_RETURN_VOID_UNLESS(bOk, L"SetPresetInstanceType失败",L"GDMPLab",L"2024-03-30");
    ut->Commit();

    // ------------------------------------------------------------------------------------------
    // 更新属性面板
    // ------------------------------------------------------------------------------------------
    GbmpPresetParameterUIUtils::UpdatePropertyPanel(pInstance->GetElementId());
    GbmpPresetParameterUIUtils::EnablePropertyPanel(true);

    // ------------------------------------------------------------------------------------------
    // 设置Ribbon上下文面板
    // ------------------------------------------------------------------------------------------
    AddStructureDynamicPannelUtil::SetupWallHoleCreationContext(this, ID_PAGE_GBMP_MODIFY);
    IMainWindow::GetMainWindow()->UpdateControlStatus();

    // ------------------------------------------------------------------------------------------
    // 订阅属性面板消息
    // ------------------------------------------------------------------------------------------
    IPropertyWidget* pPropertyPanel = IPropertyWidget::GetPropertyWidget();
    if (pPropertyPanel)
    {
        pPropertyPanel->GetInputEvent()->Add(this);
    }

    // ------------------------------------------------------------------------------------------
    // 进入子Action
    // ------------------------------------------------------------------------------------------
    GettingInstancePlacePoint();
}

void ActionCreateStructureWallHole::OnChildActionFinished(IUiView* pCurrentView, const ActionOutput& childActionReturnParam)
{
    if (m_isUserCancelled)
    {
        // 如果用户选择退出
        CleanupTempGraphicsShape();
        MarkFinishStatus(ActionFinishStatus::Successful);
        UpdateView();
        return;
    }

    gcmp::IDocument* pDoc = GetDocument(pCurrentView);
    DBG_WARN_AND_RETURN_VOID_UNLESS(pDoc != nullptr, L"无效的文档指针",L"GDMPLab",L"2024-03-30");

    OwnerPtr<IGraphicsNodeReference> pickResult = IGraphicsNodeReference::Create();
    bool bRet = GetPickResult(pCurrentView, m_placePt, *pickResult);
    if (!bRet)
    {
        GettingInstancePlacePoint();
        return;
    }
    DBG_WARN_AND_RETURN_VOID_UNLESS(pCurrentView != nullptr, L"无效的pCurrentView指针",L"GDMPLab",L"2024-03-30");
    IModelView* view = pCurrentView->GetModelView();
    DBG_WARN_AND_RETURN_VOID_UNLESS(view, L"view为空",L"GDMPLab",L"2024-03-30");

    ElementId levelId, wallId;
    Vector3d position;
    bool bPositioned = GetHolePosition(*pickResult, wallId, position);

    OwnerPtr<IUserTransaction> opUt = IUserTransaction::Create(pDoc, GBMP_TR(L"创建GBMP基于Host的点式族实例"));
    StructureWallHoleInput input = CreateDefaultInput();
    input.Position = position;
    input.HostId = wallId;
    IInstance* pWallHoleInstance = CreateWallHole(input);
    DBG_WARN_AND_RETURN_VOID_UNLESS(pWallHoleInstance, L"pWallHoleInstance为空",L"GDMPLab",L"2024-03-30");

    bool valid = m_pDoc->GetRegenerator()->RegenerateElement(pWallHoleInstance->GetElementId());
    if (valid)  //墙洞与主体有交集(否则弱提示)
    {
        opUt->Commit();
        UpdateView();
    }
    else
    {
        NotificationTypes notiType = NotificationTypes::WarningNotificationType;
        std::wstring notiText = GBMP_TR(L"墙洞超出墙的范围，请检查墙洞标高和偏移");
        NotificationUtils::PostNotification(notiType, notiText);
        opUt->Rollback();
    }

    GettingInstancePlacePoint();
}

void ActionCreateStructureWallHole::GettingInstancePlacePoint()
{
    SetPromptMessage(GBMP_TR(L"请输入洞口插入点"));

    m_isUserCancelled = false;
    m_snap = nullptr;
    gcmp::OwnerPtr<IPickPointAction> upPickPointAction(IPickPointAction::Create(
        PickPointExchangeData(&m_placePt, &m_isUserCancelled, &m_pickedPoints, &m_snap)
        , IPickPointAction::CreateMoveCallback(&ActionCreateStructureWallHole::PreviewByPlacePoint, this)));

    // 新捕捉选项设置
    OwnerPtr<ISnapContext> opSnapContext = ISnapContext::Create();
    OwnerPtr<ISnapSettings> snapSettings = opSnapContext->GetSnapSettings();
    snapSettings->SetIsSnapOff(true);
    opSnapContext->SetSnapSettings(snapSettings.get());

    upPickPointAction->SetSnapContext(opSnapContext.get());
    StartChildAction(TransferOwnership(upPickPointAction));
}

void ActionCreateStructureWallHole::PreviewByPlacePoint(IUiView* pCurrentView, const gcmp::Vector3d& pos, const gcmp::Vector3d& mousePt)
{
    OwnerPtr<IGraphicsNodeReference> pickResult = IGraphicsNodeReference::Create();
    bool bPicked = GetPickResult(pCurrentView, pos, *pickResult);
    if (bPicked)    //拾取到了主体
    {
        gcmp::ElementId wallId;
        gcmp::Vector3d holePosition;
        bool bPositioned = GetHolePosition(*pickResult, wallId, holePosition);

        OwnerPtr<IUserTransaction> opUt = IUserTransaction::Create(m_pDoc, GBMP_TR(L"创建墙洞预览对象"));
        StructureWallHoleInput input = CreateDefaultInput();
        input.Position = holePosition;
        input.CanBeShared = false;
        input.HostId = wallId;
        IInstance* pWallHoleInstance = CreateWallHole(input);
        DBG_WARN_AND_RETURN_VOID_UNLESS(pWallHoleInstance, L"pWallHoleInstance为空",L"GDMPLab",L"2024-03-30");

        bool valid = m_pDoc->GetRegenerator()->RegenerateElement(pWallHoleInstance->GetElementId());
        if (valid)  //墙洞与主体有交集(否则不做预览)
        {
            bool status = m_pDoc->GetRegenerator()->RegenerateDocument();
            DBG_WARN_AND_RETURN_VOID_UNLESS(status, L"创建墙洞预览对象失败",L"GDMPLab",L"2024-03-30");
            const IBaseGraphicsElementShapeComponent* pBaseShapeComponent = pWallHoleInstance->GetBaseGraphicsElementShapeComponent();
            DBG_WARN_AND_RETURN_VOID_UNLESS(pBaseShapeComponent, L"pBaseShapeComponent为空",L"GDMPLab",L"2024-03-30");

            const IGraphicsElementShape* pBaseGRep = pBaseShapeComponent->GetBaseGraphicsElementShape();
            DBG_WARN_AND_RETURN_VOID_UNLESS(pBaseGRep, L"无效的pBaseGRep指针",L"GDMPLab",L"2024-03-30");
            OwnerPtr<IGraphicsElementShape> baseGrep = TransferOwnershipCast<IGraphicsElementShape>(pBaseGRep->Clone());
            OwnerPtr<IGraphicsNodeGroup> baseGpGrep = GbmpGraphicsNodeUtils::TransferGRepToGGroup(TransferOwnership(baseGrep));
            opUt->Rollback();

            GraphicsNodeUtils::CleanGraphicsNodeStyleAndMaterial(m_pDoc, baseGpGrep.get());
            baseGpGrep->SetGraphicsStyleId(m_pDoc->GetGraphicsStyleManager()->GetGraphicsStyleIdByCategoryUid(BuiltInCategoryUniIdentities::BICU_TEMP_STYLE));
            baseGpGrep->SetVisibility(GraphicsNodeVisibility::Always);

            DrawTempGraphicsNodeGroup(TransferOwnership(baseGpGrep));
            UpdateView();
            return;
        }
        else
        {
            opUt->Rollback();
        }
    }

    //不合法则返回
    CleanupTempGraphicsShape();
    return;
}

bool ActionCreateStructureWallHole::GetPickResult(IUiView* pCurrentView, const Vector3d& pos, IGraphicsNodeReference& gnodeReference) const
{
    IDocument* pDocument = GetDocument(pCurrentView);
    OwnerPtr<ILine3d> ray = UiViewUtility::WorldPositionToWorldRay(pCurrentView, pos);
    std::vector<const IGraphicsElementShape*> greps = pCurrentView->GetVisibleGreps();

    OwnerPtr<WallInstanceFilter> pPickFilter(NEW_AS_OWNER_PTR(WallInstanceFilter, pDocument));
    double tolerance = UiViewUtility::GetPickTolerance(pCurrentView);

    OwnerPtr<IPickTarget> opPickTarget = IPickTarget::Create();
    opPickTarget->EnableFace();
    OwnerPtr<IPointPickContext> opPickContext = IPointPickContext::Create();
    opPickContext->SetDocument(pDocument);
    opPickContext->SetViewerContext(pCurrentView->GetViewerContext());
    opPickContext->SetGraphicsElementShapes(greps);
    opPickContext->SetRay(ray.get());
    opPickContext->SetPickTolerance(tolerance);
    opPickContext->SetPickTarget(TransferOwnership(opPickTarget));
    opPickContext->SetPickFilter(pPickFilter.get());
    OwnerPtr<IPickResult> opPickResults4Instance = IPicker::PickByPoint(opPickContext.get());
    if (opPickResults4Instance->IsEmpty())
        return false;

    gnodeReference.Set(*opPickResults4Instance->GetFront());
    return true;
}

void ActionCreateStructureWallHole::On(IActionInputEventArgs* pArgs)
{
    //啥也不用干
}

void ActionCreateStructureWallHole::On(gcmp::IPropertyPanelEventArgs* pArgs)
{
    DBG_WARN_AND_RETURN_VOID_UNLESS(pArgs, L"pArgs为空",L"GDMPLab",L"2024-03-30");
    DBG_WARN_AND_RETURN_VOID_UNLESS(m_pDoc, L"pDoc为空",L"GDMPLab",L"2024-03-30");

    if (PropertyWidgetUtils::IsComponentTypeChangedEvent(pArgs->GetPropertyItemUniqueId()))
    {
        ElementId newInstanceTypeId(StringUtil::ToNumber<Int64>(pArgs->GetNewValue()));
        DBG_WARN_AND_RETURN_VOID_UNLESS(newInstanceTypeId.IsValid(), L"newInstanceTypeId无效",L"GDMPLab",L"2024-03-30");

        ElementId newFamilyId;
        bool bOk = StructureFamilyUtils::GetFamilyIdByTypeId(m_pDoc, newInstanceTypeId, newFamilyId);
        DBG_WARN_AND_RETURN_VOID_UNLESS(bOk, L"GetFamilyNameByTypeId失败",L"GDMPLab",L"2024-03-30");

        if (m_familyId.HasSameValue(newFamilyId) && m_instanceTypeId.HasSameValue(newInstanceTypeId))
            return;

        //缓存，并设置预设参数
        m_familyId = newFamilyId;
        m_instanceTypeId = newInstanceTypeId;

        OwnerPtr<IUserTransaction> opTransaction = IUserTransaction::Create(m_pDoc, GBMP_TR(L"保存当前创建实例的族类型到预设参数"), true);
        bOk = StructureInstancePresetParameterUtils::SetPresetInstanceType(m_pDoc, m_category, m_categorySecondKey, newInstanceTypeId);
        DBG_WARN_AND_RETURN_VOID_UNLESS(bOk, L"SetPresetInstanceType失败",L"GDMPLab",L"2024-03-30");
        opTransaction->Commit();

        CleanupTempGraphicsShape();
    }
}

bool ActionCreateStructureWallHole::GetHolePosition(const IGraphicsNodeReference& pickResult, gcmp::ElementId& wallId, gcmp::Vector3d& holePosition)
{
    wallId = pickResult.GetElementId();
    const IElement* pWall = m_pDoc->GetElement(wallId);
    DBG_WARN_AND_RETURN_FALSE_UNLESS(pWall, L"pWallInstance为空",L"GDMPLab",L"2024-03-30");

    const IElementPosition *posBehavior = pWall->GetElementPosition();
    DBG_WARN_AND_RETURN_FALSE_UNLESS(posBehavior, L"posBehavior为空",L"GDMPLab",L"2024-03-30");
    Vector3d intersectPoint = pickResult.GetTransformedIntersectPoint();

    const IPositionCurve2d*  pPositionCurve2d = dynamic_cast<const IPositionCurve2d*>(posBehavior->GetPositionGeometry());
    DBG_WARN_AND_RETURN_FALSE_UNLESS(pPositionCurve2d, L"无效pPositionCurve2d",L"GDMPLab",L"2024-03-30");
    const ICurve2d* pBaseCurve = pPositionCurve2d->GetBaseCurve();

    Vector2d queryPt(intersectPoint.X(), intersectPoint.Y());
    Vector2d middlePt;
    bool bLimit = true;
    double middleParam;
    double minDis;
    bool status = AlgorithmMinimalDistance::Calculate(pBaseCurve, queryPt, middlePt, bLimit, &middleParam, minDis);
    DBG_WARN_AND_RETURN_FALSE_UNLESS(status, L"放置墙洞的插入点不正确",L"GDMPLab",L"2024-03-30");

    holePosition.SetX(middlePt.X());
    holePosition.SetY(middlePt.Y());

    //Z值的计算：参照标高 + 标高偏移
    ElementId preSetWallHoleId = GetPreSetElementId();
    IElement* pElement = m_pDoc->GetElement(preSetWallHoleId);
    const IElementPosition* position = pElement->GetElementPosition();
    double planeHeight = position->GetBaseAssociatedPlaneHeight();
    double planeOffset = position->GetBasePlaneOffset();
    holePosition.SetZ(planeHeight + planeOffset);

    return true; 
}

IInstance* ActionCreateStructureWallHole::CreateWallHole(const StructureWallHoleInput& input)
{
    IStructureWallHole* pWallHole = IStructureWallHole::Create(&input);
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pWallHole, L"pWallHole为空",L"GDMPLab",L"2024-03-30");

    IInstance* pWallHoleInstance = dynamic_cast<IInstance*>(m_pDoc->GetElement(pWallHole->GetElementId()));
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pWallHoleInstance, L"pWallHoleInstance为空",L"GDMPLab",L"2024-03-30");

    return pWallHoleInstance;
}

IDocument* ActionCreateStructureWallHole::GetDocument(gcmp::IUiView* pCurrentView) const
{
    IModelView* pModelView = pCurrentView->GetModelView();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pModelView, L"pModelView为空",L"GDMPLab",L"2024-03-30");

    IDocument* pDoc = pModelView->GetDocument();
    DBG_WARN_AND_RETURN_NULLPTR_UNLESS(pDoc, L"无效的文档指针",L"GDMPLab",L"2024-03-30");

    return pDoc;
}

StructureWallHoleInput ActionCreateStructureWallHole::CreateDefaultInput()
{
    StructureWallHoleInput wallHoleInput;
    StructureInstanceCmdUtils::GenerateStructureWallHoleInput(m_pDoc, m_pCurrentView,
        m_category, m_categorySecondKey, m_familyId, m_instanceTypeId, wallHoleInput);

    return wallHoleInput;
}